package view.test;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.Timer;

import model.GameBoard;
import model.GameModel;
import model.IGameModelObserver;
import model.entities.Direction;
import model.entities.LaserBeam;
import model.entities.LaserBeamSegment;
import model.entities.LaserSource;
import model.entities.Mirror;
import model.entities.MirrorOrientation;
import model.entities.Wall;
import model.entities.targets.ITarget;
import model.entities.targets.TargetType;
import util.SpriteUtils;
import view.entities.MirrorFactory;
import view.entities.ViewEntity;
import view.entities.WallFactory;
import controller.GameController;

/**
 * This represents the view of the model's gameboard
 * using a nxn array of ViewEntites 
 * 
 * @author ben
 *
 */
public class GameBoardPanel extends JPanel implements IGameModelObserver {

	private static final long serialVersionUID = 6228383279606989664L;
	
	private ViewEntity[][] gameBoard;
	
	// not yet implemented. in progress
	private boolean classicMode = false;
	
	Map<MirrorOrientation, ImageIcon> mirrorSpriteMap = new HashMap<MirrorOrientation, ImageIcon>();
	Map<Direction, ImageIcon> sourceSpriteMap = new HashMap<Direction, ImageIcon>();
	Map<TargetType, ImageIcon> targetSpriteMap = new HashMap<TargetType, ImageIcon>();
	ImageIcon wall;
	
	private boolean laserAnimationOn = true;
	Timer timer;
	private int beamDarkness = 0;
	private boolean darkening = true;
	private boolean gridOn = false;
	private GameModel model;
	private GameController controller;
	private GlassPane pane;
	
	public GameBoardPanel(GameModel inModel, GameController controller, GlassPane pane) {
		
		this.model = inModel;
		this.controller = controller;
		this.pane = pane;
		Dimension boardDimension = model.getGameBoard().getDimension();
		this.setSize(600, 600); // dont leave this as magic numbers!! TODO
		LayoutManager layout = new GridLayout(boardDimension.width, boardDimension.height);
		this.setLayout(layout);
		this.setBackground(Color.BLACK);
		this.setLocation(new Point(0, 8));
		this.setMaximumSize(new Dimension(600, 600)); // dont leave this as magic numbers!! TODO
		this.setBorder(BorderFactory.createLoweredBevelBorder());
		this.setVisible(true);
		
		gameBoard = new ViewEntity[boardDimension.width][boardDimension.height];
		
		loadSprites();
		
		fillBoard(boardDimension);
		
		timer = new Timer(150, new ActionListener() {
			
			@Override
			public void actionPerformed(ActionEvent e) {
				if (darkening)
					beamDarkness++;
				else
					beamDarkness--;
				if (beamDarkness == 3) {  
					darkening = false;
				}
				else if (beamDarkness == 0) {
					darkening = true;
				}
				repaint();				
			}
		});
		
		timer.start();
	}
	
	private void fillBoard(Dimension boardDimension) {
						
		for (int i = 0; i < boardDimension.width; i++) {
			for (int j = 0; j < boardDimension.height; j++) {
				
				final Point cell = new Point(j,i);
				ViewEntity entity = new ViewEntity(cell, controller, pane);		
				gameBoard[j][i] = entity;
				this.add(entity);
				
			}
			
		}
	}
	
	private void resetBoard() {
		for (ViewEntity[] entityList : gameBoard) {
			for (ViewEntity entity : entityList) {
				entity.setEntityIcon(null);
				if(gridOn)
					entity.setBorder(BorderFactory.createLineBorder(Color.ORANGE.darker().darker().darker()));
				else 
					entity.setBorder(null);
			}
		}
	}
	
	private void loadSprites() {
		
		if(classicMode)
			return;
		
		ImageIcon posMirror = SpriteUtils.loadSprite("/MIRROR_RISE.png",this);
		mirrorSpriteMap.put(MirrorOrientation.POSITIVE_SLOPE, posMirror);
		
		ImageIcon horMirror = SpriteUtils.loadSprite("/MIRROR_HOR.png",this);
		mirrorSpriteMap.put(MirrorOrientation.HORIZONTAL, horMirror);
		
		ImageIcon negMirror = SpriteUtils.loadSprite("/MIRROR_DECLINE.png",this);
		mirrorSpriteMap.put(MirrorOrientation.NEGATIVE_SLOPE, negMirror);
		
		ImageIcon vertMirror = SpriteUtils.loadSprite("/MIRROR_VERT.png",this);
		mirrorSpriteMap.put(MirrorOrientation.VERTICAL, vertMirror);
	
		ImageIcon steepRiseMirror = SpriteUtils.loadSprite("/MIRROR_STEEP_RISE.png",this);
		mirrorSpriteMap.put(MirrorOrientation.STEEP_RISE, steepRiseMirror);
		
		ImageIcon slightDeclineMirror = SpriteUtils.loadSprite("/MIRROR_SLIGHT_DECLINE.png",this);
		mirrorSpriteMap.put(MirrorOrientation.SLIGHT_DECLINE, slightDeclineMirror);		
		
		ImageIcon slightRiseMirror = SpriteUtils.loadSprite("/MIRROR_SLIGHT_RISE.png",this);
		mirrorSpriteMap.put(MirrorOrientation.SLIGHT_RISE, slightRiseMirror);
		
		ImageIcon steepDeclineMirror = SpriteUtils.loadSprite("/MIRROR_STEEP_DECLINE.png",this);
		mirrorSpriteMap.put(MirrorOrientation.STEEP_DECLINE, steepDeclineMirror);
		
		ImageIcon sourceRight = SpriteUtils.loadSprite("/source_right.png",this);
		sourceSpriteMap.put(Direction.RIGHT, sourceRight);
		
		ImageIcon sourceBottomRight = SpriteUtils.loadSprite("/source_bottom_right.png",this);
		sourceSpriteMap.put(Direction.BOTTOM_RIGHT, sourceBottomRight);
		
		ImageIcon sourceBottom = SpriteUtils.loadSprite("/source_bottom.png",this);
		sourceSpriteMap.put(Direction.BOTTOM, sourceBottom);
		
		ImageIcon sourceBottomLeft = SpriteUtils.loadSprite("/source_bottom_left.png",this);
		sourceSpriteMap.put(Direction.BOTTOM_LEFT, sourceBottomLeft);
		
		ImageIcon sourceLeft = SpriteUtils.loadSprite("/source_left.png",this);
		sourceSpriteMap.put(Direction.LEFT, sourceLeft);
		
		ImageIcon sourceTopLeft = SpriteUtils.loadSprite("/source_top_left.png",this);
		sourceSpriteMap.put(Direction.TOP_LEFT, sourceTopLeft);
		
		ImageIcon sourceTop = SpriteUtils.loadSprite("/source_top.png",this);
		sourceSpriteMap.put(Direction.TOP, sourceTop);
		
		ImageIcon sourceTopRight = SpriteUtils.loadSprite("/source_top_right.png",this);
		sourceSpriteMap.put(Direction.TOP_RIGHT, sourceTopRight);
		
		ImageIcon transparentTargetRedUnSat = SpriteUtils.loadSprite("/transparent_target_red_unsatisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_RED, transparentTargetRedUnSat);
		
		ImageIcon transparentTargetRedSat = SpriteUtils.loadSprite("/transparent_target_red_satisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_RED_SAT, transparentTargetRedSat);

		ImageIcon transparentTargetYellowUnSat = SpriteUtils.loadSprite("/transparent_target_yellow_unsatisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_YELLOW, transparentTargetYellowUnSat);
		
		ImageIcon transparentTargetYellowSat = SpriteUtils.loadSprite("/transparent_target_yellow_satisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_YELLOW_SAT, transparentTargetYellowSat);
		
		ImageIcon transparentTargetBlueUnSat = SpriteUtils.loadSprite("/transparent_target_blue_unsatisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_BLUE, transparentTargetBlueUnSat);
		
		ImageIcon transparentTargetBlueSat = SpriteUtils.loadSprite("/transparent_target_blue_satisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_BLUE_SAT, transparentTargetBlueSat);
		
		ImageIcon transparentTargetGreenUnSat = SpriteUtils.loadSprite("/transparent_target_green_unsatisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_GREEN, transparentTargetGreenUnSat);
		
		ImageIcon transparentTargetGreenSat = SpriteUtils.loadSprite("/transparent_target_green_satisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_GREEN_SAT, transparentTargetGreenSat);
		
		ImageIcon transparentTargetMagentaUnSat = SpriteUtils.loadSprite("/transparent_target_magenta_unsatisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_MAGENTA, transparentTargetMagentaUnSat);
		
		ImageIcon transparentTargetMagentaSat = SpriteUtils.loadSprite("/transparent_target_magenta_satisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_MAGENTA_SAT, transparentTargetMagentaSat);
		
		ImageIcon transparentTargetCyanUnSat = SpriteUtils.loadSprite("/transparent_target_cyan_unsatisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_CYAN, transparentTargetCyanUnSat);
		
		ImageIcon transparentTargetCyanSat = SpriteUtils.loadSprite("/transparent_target_cyan_satisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_CYAN_SAT, transparentTargetCyanSat);
		
		ImageIcon transparentTargetWhiteSat = SpriteUtils.loadSprite("/transparent_target_white_satisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_WHITE_SAT, transparentTargetWhiteSat);
		
		ImageIcon transparentTargetWhiteUnSat = SpriteUtils.loadSprite("/transparent_target_white_unsatisfied.png",this);
		targetSpriteMap.put(TargetType.TRANSPARENT_WHITE, transparentTargetWhiteUnSat);
		
		wall = SpriteUtils.loadSprite("/wall.png",this);


	}

	@Override
	public void onGameModelChange() {
		
		resetBoard();
		paintEntities();
		repaint();
		
		if (model.isWon())
			JOptionPane.showMessageDialog(this, "Amazing! You Solved It!");
		
	}
	
	/**
	 * pain everything but the lasers
	 */
	private void paintEntities() {
		
		GameBoard worldBoard = model.getGameBoard();
		Map<Point, LaserSource> sourceMap = worldBoard.getSourceMap();
		Map<Point, Wall> wallMap = worldBoard.getWallMap();
		Map<Point, Mirror> mirrorMap = worldBoard.getMirrorMap();
		Map<Point, ITarget> targetMap = worldBoard.getTargetMap();
		
		Set<Point> sourceSet = sourceMap.keySet();
		Set<Point> wallSet = wallMap.keySet();
		Set<Point> mirrorSet = mirrorMap.keySet();
		Set<Point> targetSet = targetMap.keySet();
		
		for (Point cell : mirrorSet) {
			Mirror mirror = mirrorMap.get(cell);
			MirrorOrientation orientation = mirror.getMirrorOrientation();
			
			ViewEntity entity = gameBoard[cell.x][cell.y];
			entity.setEntityIcon(null);
			
			entity.setEntityIcon(mirrorSpriteMap.get(orientation));
			if (classicMode)
			entity.setViewFactory(new MirrorFactory(orientation, entity.getSize()));
			
		}	
		
		for (Point cell : wallSet) {
			gameBoard[cell.x][cell.y].setEntityIcon(wall);
			ViewEntity entity = gameBoard[cell.x][cell.y];
			if (classicMode)
			entity.setViewFactory(new WallFactory(entity.getSize()));
			gameBoard[cell.x][cell.y].setBorder(null);
		}
		
		for (Point cell : sourceSet) {
			LaserSource source = sourceMap.get(cell);
			Direction direction = source.getDirecton();
			
			ViewEntity label = gameBoard[cell.x][cell.y];
			label.setText(null);
			label.setEntityIcon(null);
			label.setEntityIcon(sourceSpriteMap.get(direction));
			
		}	
		
		for (Point cell : targetSet) {
			ITarget target = targetMap.get(cell);
			Color targetColor = target.getColor();
			boolean isSatisfied = target.isSatisfied();
			
			ViewEntity label = gameBoard[cell.x][cell.y];

			if (targetColor.equals(Color.RED)) {
				if(isSatisfied)
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_RED_SAT));
				else
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_RED));
			}
			
			if (targetColor.equals(Color.YELLOW)) {
				if(isSatisfied)
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_YELLOW_SAT));
				else
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_YELLOW));
			}
			
			if (targetColor.equals(Color.BLUE)) {
				if(isSatisfied)
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_BLUE_SAT));
				else
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_BLUE));
			}
			
			if (targetColor.equals(Color.GREEN)) {
				if(isSatisfied)
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_GREEN_SAT));
				else
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_GREEN));
			}
			
			if (targetColor.equals(Color.MAGENTA)) {
				if(isSatisfied)
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_MAGENTA_SAT));
				else
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_MAGENTA));
			}
			
			if (targetColor.equals(Color.CYAN)) {
				if(isSatisfied)
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_CYAN_SAT));
				else
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_CYAN));
			}
			
			if (targetColor.equals(Color.WHITE)) {
				if(isSatisfied)
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_WHITE_SAT));
				else
					label.setEntityIcon(targetSpriteMap.get(TargetType.TRANSPARENT_WHITE));
			}
			
		}
	}
	
	/**
	 * laser beams get drawn here
	 */
	@Override
	public void paintComponent(Graphics g) {
		
		super.paintComponent(g);
		Graphics2D g2d = (Graphics2D) g;
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
		List<LaserBeam> beamList = model.getLaserBeamList();
		
		Graphics2D g2 = (Graphics2D) g2d.create(); 
		
		g2.setStroke(new BasicStroke(3f,2,2));
//		WobbleStroke wobbleStroke = new WobbleStroke(10f, 0.5f);
//		g2.setStroke(wobbleStroke);
		
		for (LaserBeam beam : beamList) {
			List<LaserBeamSegment> segments = beam.getSegmentList();
			
			Color color = beam.getColor();
			
			for (int i = 0; i < beamDarkness; i++)
				color = color.darker();
			
			g2.setColor(color);
			for (LaserBeamSegment segment : segments) {
				
				JLabel label = gameBoard[segment.getStartCell().x][segment.getStartCell().y];
				Point startCellLocation = label.getLocation();
				int startPixX = startCellLocation.x + label.getWidth() / 2; 
				int startPixY = startCellLocation.y + label.getHeight() / 2; 
				
				JLabel labelEnd = gameBoard[segment.getEndCell().x][segment.getEndCell().y];
				Point endCellLocation = labelEnd.getLocation();
				
				int endPixX = 0;
				int endPixY = 0;
				
				if (segment.isHitEdge())
				switch(segment.getDirection().invert()) {
				case BOTTOM : endPixX = endCellLocation.x + labelEnd.getWidth() / 2; 
					endPixY = endCellLocation.y; break;
				case BOTTOM_LEFT : endPixX = endCellLocation.x + labelEnd.getWidth();
					endPixY = endCellLocation.y; break;
				case LEFT : endPixX = endCellLocation.x + labelEnd.getWidth(); 
					endPixY = endCellLocation.y + labelEnd.getHeight() / 2; break;
				case TOP_LEFT : endPixX = endCellLocation.x + labelEnd.getWidth(); 
					endPixY = endCellLocation.y + labelEnd.getHeight(); break;
				case TOP : endPixX = endCellLocation.x + labelEnd.getWidth() / 2; 
					endPixY = endCellLocation.y + labelEnd.getHeight(); break;
				case TOP_RIGHT : endPixX = endCellLocation.x; 
					endPixY = endCellLocation.y + labelEnd.getHeight(); break;
				case RIGHT : endPixX = endCellLocation.x; 
					endPixY = endCellLocation.y + labelEnd.getHeight() / 2; break;
				case BOTTOM_RIGHT : endPixX = endCellLocation.x; 
					endPixY = endCellLocation.y; break;
				}
				else {
					endPixX = endCellLocation.x + labelEnd.getWidth() / 2; 
					endPixY = endCellLocation.y + labelEnd.getHeight() / 2;
				}
				
				g2.drawLine(startPixX, startPixY, endPixX, endPixY);
								
			}
		}
			
		
	}
	
	public boolean isGridOn() {
		return gridOn;
	}

	public void setGridOn(boolean gridOn) {
		this.gridOn = gridOn;
	}

	public ViewEntity[][] getGameBoard() {
		return gameBoard;
	}

	public void setLaserAnimationOn(boolean laserAnimationOn) {
		if (laserAnimationOn) {
			timer.start();
		}
		else {
			timer.stop();
		}
	}
	
}
